#pragma once
#include<mutex>
#include<condition_variable>
#include<queue>
#include<string>
#include<sstream>
#include<atomic>
#include<thread>
#include<fstream>
#include<stdexcept>

template<typename T>
std::string to_string(T&& msg) {
	std::stringstream ss;
	ss << std::forward<T>(msg);
	return ss.str();
}

class LogQueue
{
public:
	void push(const std::string& msg) {
		std::lock_guard<std::mutex> lock(_mtx);

		_que.push(msg);
		_cv.notify_one();
	}
	bool pop(std::string& msg) {
		std::unique_lock<std::mutex> lock(_mtx);
		_cv.wait(lock, [this]() {
			return !_que.empty() || _flag;
		});
		if (_que.empty() && _flag)
			return false;

		msg = _que.front();
		_que.pop();
		return true;
	}
	void shutdown() {
		_flag.store(true);
		_cv.notify_all();
	}
private:
	std::queue<std::string> _que;
	std::mutex _mtx;
	std::condition_variable _cv;
	std::atomic<bool> _flag=false;
};

class Logger {
public:
	Logger(const std::string& filename) : _flag(false), _log_file(filename, std::ios::out | std::ios::app) {
		
		if (!_log_file.is_open()) {
			throw std::runtime_error("Fail to open is " + filename);
		}

		_thread = std::thread([this]() {
			std::string msg;
			while (_logque.pop(msg)) {

				_log_file << msg << std::endl;
			}
		});
	}

	~Logger() {
		_flag = true;
		_logque.shutdown();
		if (_thread.joinable()) {
			_thread.join();
		}
		
		if (_log_file.is_open()) {
			_log_file.close();
		}
	}

	template<typename ...Args>
	void log(const std::string& format, Args&&... args) {

		_logque.push(formatMessage(format, std::forward<Args>(args)...));
	}


private:
	template<typename ...Args>
	std::string formatMessage(const std::string& format, Args&&... args) {
		std::vector<std::string> arg_strings{ to_string(std::forward<Args>(args))... };
		std::stringstream ss;
		size_t arg_index = 0;
		size_t pos = 0;
		size_t placeholder = format.find("{}", pos);
		while (placeholder != std::string::npos) {
			ss << format.substr(pos, placeholder - pos);
			if (arg_index < arg_strings.size()) {
				ss << arg_strings[arg_index++];
			}
			else {
				ss <<"{}";
			}
			pos = placeholder + 2;
			placeholder = format.find("{}", pos);
		}
		ss << format.substr(pos);
		while (arg_index < arg_strings.size()) {
			ss << arg_strings[arg_index++];
		}
		return ss.str();
	}

	std::fstream _log_file;
	std::thread _thread;
	LogQueue _logque;
	std::atomic<bool> _flag;
};

